﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Runtime.Serialization;
using System.Reflection;
using Snowflake.Common;

namespace Snowflake.Entity
{
    /// <summary>
    /// Base class of type EntityObject for all table entity mappings
    /// </summary>
    [Serializable]
    public abstract partial class EntityObject<TEntity> where TEntity : EntityObject<TEntity>, new()
    {
        #region Members
        private RestorableValueManager _RestorableValueManager;

        private EntityList<TEntity> _EntityList;

        private RestorableValue<EntityObjectState> _State;

        private IndexedDictionary<string, object> _ExtendedProperties;
        #endregion

        #region Events
        /// <summary>
        /// This event is rised, when some of the field of the entity object is changing
        /// </summary>
        public event EventHandler<PropertyChangingEventArgs> PropertyChanging = delegate { };
        /// <summary>
        /// This event is rised, when some of the field of the entity object is changed
        /// </summary>
        public event EventHandler<PropertyChangedEventArgs> PropertyChanged = delegate { };
        #endregion

        #region Constructor
        /// <summary>
        /// Initialize the new instance of the Snowflake.Entity.EnityObject class with state detached
        /// </summary>
        public EntityObject()
        {
            this._RestorableValueManager = new RestorableValueManager();
            this._State = new RestorableValue<EntityObjectState>("State", EntityObjectState.Detached);
            this._RestorableValueManager.Add(this._State);
        }
        #endregion

        #region NewField Factory Methods
        /// <summary>
        /// Factory method that create new EntityField by PropertyName without value
        /// </summary>
        /// <typeparam name="T">Type of the value</typeparam>
        /// <param name="columnName">Name of the column which is owner of the value</param>
        /// <returns>Value of the property</returns>
        protected EntityField<T> NewField<T>(string columnName)
        {
            return new EntityField<T>(this, columnName);
        }
        /// <summary>
        /// Factory method that create new EntityField by PropertyName with value
        /// </summary>
        /// <typeparam name="T">Type of the value</typeparam>
        /// <param name="columnName">Name of the column which is owner of the value</param>
        /// <param name="value">The initalizing value</param>
        /// <returns>Value of the property</returns>
        protected EntityField<T> NewField<T>(string columnName, T value)
        {
            return new EntityField<T>(this, columnName, value);
        }
        #endregion

        #region RestorableValueManager Property
        internal RestorableValueManager RestorableValueManager
        {
            get { return this._RestorableValueManager; }
        }
        #endregion

        #region Indexed Property
        /// <summary>
        /// Gets or sets extended properties of the object
        /// </summary>
        /// <param name="property">The name of the extended property</param>
        /// <returns>Return the value of specified extended proeprty</returns>
        public object this[string property]
        {
            get
            {
                if (this._ExtendedProperties == null)
                    this._ExtendedProperties = new IndexedDictionary<string, object>();

                return this._ExtendedProperties[property];
            }
            set
            {
                if (this._ExtendedProperties == null)
                    this._ExtendedProperties = new IndexedDictionary<string, object>();

                this._ExtendedProperties[property] = value;
            }
        }
        #endregion

        #region EntityList Property
        /// <summary>
        /// The EntityList, where the Entity Object is included.
        /// </summary>
        public EntityList<TEntity> EntityList
        {
            internal set
            {
                this._EntityList = value;
            }
            get
            {
                return this._EntityList;
            }
        }
        #endregion

        #region OriginalObject Property
        internal TEntity OriginalObject
        {
            get
            {
                TEntity original = new TEntity();
                original.RestorableValueManager.InitalizeFromOriginalStateOf(this._RestorableValueManager);
                return original;
            }
        }
        #endregion

        #region ResetValues
        internal void ResetValues()
        {
            TEntity original = new TEntity();
            this._RestorableValueManager.InitalizeFromOriginalStateOf(original.RestorableValueManager);
        }
        #endregion

        #region State
        /// <summary>
        /// State of the object depeding on the changes made over it
        /// </summary>
        public EntityObjectState State
        {
            get { return this._State; }
            internal set
            {
                this._State = value;
            }
        }
        #endregion

        #region AcceptChanges
        /// <summary>
        /// Accept all changes of the object and set the state of the object to Unchanged
        /// </summary>
        public void AcceptChanges()
        {
            this.InvalidateInstance();

            if (this._State == EntityObjectState.Detached)
                throw new InvalidOperationException("Cannot perform the operation due to the row is not in the entitylist.");

            this._EntityList.AcceptChanges(this as TEntity);

        }
        #endregion

        #region RejectChanges
        /// <summary>
        /// Reject all changes of the object and returns old values
        /// </summary>
        public void RejectChanges()
        {
            this.InvalidateInstance();
            this._EntityList.RejectChanges(this as TEntity);
        }
        #endregion

        #region SetAdded
        /// <summary>
        /// Sets the state of the object to Added
        /// </summary>
        public void SetAdded()
        {
            this.InvalidateInstance();

            if (this._State != EntityObjectState.Unchanged)
                throw new InvalidOperationException("Added state cannot be set due to current state '" + this._State + "'");

            this._State = EntityObjectState.Added;

        }
        #endregion

        #region Delete
        /// <summary>
        /// Sets the state of the object to Deleted
        /// </summary>
        public void Delete()
        {
            this.InvalidateInstance();
            this._EntityList.Delete(this as TEntity);
        }
        #endregion

        #region InvalidateInstance
        private void InvalidateInstance()
        {
            if (this._EntityList == null)
                throw new MissingEntityListReferenceException("The object doesn't have reference to EntityTable, please use NewEntityMethod of EntityList<T>");
        }
        #endregion

        #region Abstract and Virtual Methods
        /// <summary>
        /// Validate the value of the property during changing of the field
        /// </summary>
        /// <typeparam name="TValue">The type of the value</typeparam>
        /// <param name="property">The name of the property that is validating</param>
        /// <param name="value">The value that will be validated</param>
        /// <returns>True for valid value, otherwise false for invalid value.</returns>
        protected virtual bool IsValidPropertyValue<TValue>(string property, TValue value)
        {
            return true;
        }
        #endregion

    }
}
